using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using Rarous.NForms.Helpers;

namespace Rarous.NForms {
  /// <summary>
  /// Třída reprezentující HTML element.
  /// </summary>
  public class HtmlElement  : IEnumerable<HtmlElement>  {
    public const bool SelfClosing = false;
    private const string ClassAttribute = "Class";
    private const string TextAttribute = "text";
    private static readonly IList<string> emtyElements = 
      new List<string> { "img", "hr", "br", "input", "meta", "area", "base", "col", "link", "param", 
                         "basefont", "frame", "isindex", "wbr", "embed" };
    private IList<HtmlElement> children;
    private string tag;
    private bool isPairElement;

    /// <summary>
    /// Konstruktor. Vytvoří novou instanci HtmlElement.
    /// </summary>
    public HtmlElement()
      : this("span") {
    }

    /// <summary>
    /// Konstruktor. Vytvoří novou instanci HtmlElement.
    /// </summary>
    /// <param name="tag">Název elementu.</param>
    public HtmlElement(string tag)
      : this(tag, null) {
    }

    /// <summary>
    /// Konstruktor. Vytvoří novou instanci HtmlElement.
    /// </summary>
    /// <param name="attributes">Objekt s atributy elementu.</param>
    /// <param name="tag">Název elementu.</param>
    public HtmlElement(string tag, object attributes)
      : this(tag, attributes, !emtyElements.Contains(tag)) {
    }

    /// <summary>
    /// Konstruktor. Vytvoří novou instanci HtmlElement.
    /// </summary>
    /// <param name="attributes">Slovník s atributy elementu.</param>
    /// <param name="tag">Název elementu.</param>
    public HtmlElement(string tag, IDictionary<string, object> attributes)
      : this(tag, attributes, !emtyElements.Contains(tag)) {
    }

    /// <summary>
    /// Konstruktor. Vytvoří novou instanci HtmlElement.
    /// </summary>
    /// <param name="tag">Název elementu.</param>
    /// <param name="attributes">Slovník s atributy elementu.</param>
    /// <param name="isPairElement">Je element párový?</param>
    public HtmlElement(string tag, IDictionary<string, object> attributes, bool isPairElement) {
      Attributes = new Dictionary<string, object>();
      this.children = new List<HtmlElement>();
      this.tag = tag.Trim().ToLower();
      this.isPairElement = isPairElement;

      LoadAtributes(attributes);
    }

    /// <summary>
    /// Konstruktor. Vytvoří novou instanci HtmlElement.
    /// </summary>
    /// <param name="tag">Název elementu.</param>
    /// <param name="attributes">Objekt s atributy elementu.</param>
    /// <param name="isPairElement">Je element párový?</param>
    public HtmlElement(string tag, object attributes, bool isPairElement)
      : this(tag, null, isPairElement) {
      LoadAtributes(attributes);
    }

    /// <summary>
    /// Indexer. Prochází potomky podle jejich indexu.
    /// </summary>
    /// <param name="index">Index potomka.</param>
    /// <returns></returns>
    public HtmlElement this[int index] {
      get {
        return children[index];
      }
    }

    /// <summary>
    /// Indexer. Prozhází atributy podle jejich názvu.
    /// </summary>
    /// <param name="attributeName">Název atributu.</param>
    /// <returns></returns>
    public object this[string attributeName] {
      get {
        object value = null;
        Attributes.TryGetValue(attributeName, out value);
        return value;
      }
      set {
        Attributes[attributeName] = value.HtmlEncode();
      }
    }

    /// <summary>
    /// Vrací slovník atributů a jejich hodnot.
    /// </summary>
    public Dictionary<string, object> Attributes { get; private set; }

    /// <summary>
    /// Vrací zavírací tag elementu.
    /// </summary>
    public string ClosingTag {
      get {
        if (String.IsNullOrEmpty(tag)) {
          return String.Empty;
        }
        return isPairElement ? String.Format("</{0}>", tag) : String.Empty;
      }
    }

    /// <summary>
    /// Vrací otevírací tag elementu.
    /// </summary>
    public string OpeningTag {
      get {
        if (String.IsNullOrEmpty(tag)) {
          return String.Empty;
        }

        StringBuilder sb = new StringBuilder("<");
        sb.Append(tag);

        foreach (string key in Attributes.Keys) {
          if (key.ToLowerInvariant() == TextAttribute)
            continue;
          sb.AppendFormat(" {0}=\"{1}\"", key.ToLowerInvariant(), Attributes[key].HtmlEncode());
        }

        if (isPairElement) {
          sb.Append(">");
        }
        else {
          sb.Append(" />");
        }

        return sb.ToString();
      }
    }

    /// <summary>
    /// Vrací počet potomků.
    /// </summary>
    public int ChildrenCount {
      get {
        return children.Count;
      }
    }

    /// <summary>
    /// Vrací nadřazený element.
    /// </summary>
    public HtmlElement Parent {
      get;
      private set;
    }

    /// <summary>
    /// Vrací text elementu.
    /// </summary>
    public string Text {
      get;
      private set;
    }

    protected internal void LoadAtributes(IDictionary<string, object> attributes) {
      foreach (string key in attributes.Keys) {
        if (key.ToLowerInvariant() == TextAttribute) {
          Text = attributes[key].HtmlEncode();
        }
        Attributes.Add(key, attributes[key].HtmlEncode());
      }
    }

    protected internal void LoadAtributes(object attributes) {
      if (attributes != null) {
        IDictionary<string, object> attrs = attributes.ToDictionary();
        LoadAtributes(attrs);
      }
    }

    /// <summary>
    /// Přidá element do kolekce potomků.
    /// </summary>
    /// <param name="child">Vkládaný podelement.</param>
    /// <returns></returns>
    public HtmlElement AddChild(HtmlElement child) {
      children.Add(child);
      child.Parent = this;
      return this;
    }

    /// <summary>
    /// Přidá element do kolekce potomků.
    /// </summary>
    /// <param name="tag">Název elementu.</param>
    public HtmlElement AddChild(string tag) {
      return AddChild(tag, null);
    }



    /// <summary>
    /// Přidá element do kolekce potomků.
    /// </summary>
    /// <param name="tag">Název elementu.</param>
    /// <param name="attributes">Objekt s atributy elementu.<</param>
    public HtmlElement AddChild(string tag, object attributes) {
      return AddChild(tag, attributes, !emtyElements.Contains(tag));
    }

    /// <summary>
    /// Přidá element do kolekce potomků.
    /// </summary>
    /// <param name="tag">Název elementu.</param>
    /// <param name="isPairElement">Je element párový?</param>
    /// <param name="attributes">Objekt s atributy elementu.<</param>
    public HtmlElement AddChild(string tag, object attributes, bool isPairElement) {
      return AddChild(new HtmlElement(tag, attributes, isPairElement));
    }

    /// <summary>
    /// Přídá elementu CSS třídu.
    /// </summary>
    /// <param name="className">Název CSS třídy.</param>
    /// <returns></returns>
    public HtmlElement AddClass(string className) {
      if (Attributes.ContainsKey(ClassAttribute)) {
        Attributes[ClassAttribute] = String.Format("{0} {1}", className, Attributes[ClassAttribute]);
      }
      else {
        Attributes[ClassAttribute] = className;
      }
      return this;
    }

    /// <summary>
    /// Odebere CSS třídu elementu.
    /// </summary>
    /// <param name="className">Název CSS třídy.</param>
    /// <returns></returns>
    public HtmlElement RemoveClass(string className) {
      object o;
      if (Attributes.TryGetValue(ClassAttribute, out o)) {
        string[] classNames = o.ToString().Split(' ');
        StringBuilder sb = new StringBuilder();
        foreach (string name in classNames) {
          if (String.CompareOrdinal(className, name) == 0) {
            continue;
          }
          sb.Append(name).Append(" ");
        }
        Attributes[ClassAttribute] = sb.ToString().Trim();
      }
      return this;
    }

    /// <summary>
    /// Zjistí, zda má element nastavenou dannou třídu.
    /// </summary>
    /// <param name="className">Název CSS třídy.</param>
    /// <returns></returns>
    public bool HasClass(string className) {
      object o;
      if (Attributes.TryGetValue(ClassAttribute, out o)) {
        string[] classNames = o.ToString().Split(' ');
        foreach (string name in classNames) {
          if (String.CompareOrdinal(className, name) == 0) {
            return true;
          }
        }
      }
      return false;
    }

    /// <summary>
    /// Nastaví text elementu.
    /// </summary>
    /// <param name="text">Vkládaný text elementu.</param>
    /// <returns></returns>
    public HtmlElement SetText(string text) {
      return SetText(text, false);
    }

    /// <summary>
    /// Nastaví HTML elementu.
    /// </summary>
    /// <param name="html">Vkládané HTML elementu.</param>
    /// <returns></returns>
    public HtmlElement SetHtml(string html) {
      return SetText(html, true);
    }

    /// <summary>
    /// Nastaví text elementu.
    /// </summary>
    /// <param name="text">Vkládaný text elementu.</param>
    /// <param name="isHtml">Je vkládaný text HTML?</param>
    /// <remarks>Pokud je vkládaný text HTML, vloží se jak je, jinak dojde k jeho enkódování na HTML entity.</remarks>
    /// <returns></returns>
    public HtmlElement SetText(string text, bool isHtml) {
      Text = isHtml ? text : text.HtmlEncode();
      return this;
    }

    /// <summary>
    /// Vrací textovou reprezentaci aktuálního elementu.
    /// </summary>
    /// <returns></returns>
    public override string ToString() {
      if (String.IsNullOrEmpty(Text)) {
        StringBuilder sb = new StringBuilder();

        sb.Append(OpeningTag);
        children.Each(el => sb.Append(el));
        sb.Append(ClosingTag);

        return sb.ToString();
      }
      else {
        return OpeningTag + Text + ClosingTag;
      }
    }

    #region IEnumerable<HtmlElement> Members
    /// <summary>
    /// Vrací Enumerátor nad kolekcí potomků elemetu.
    /// </summary>
    /// <returns></returns>
    public IEnumerator<HtmlElement> GetEnumerator() {
      return children.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator() {
      return ((IEnumerable)children).GetEnumerator();
    }

    #endregion
  }
}
